﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using IlianaRuler.DAL;
using IlianaRuler.Utils;

namespace IlianaRuler.CPL
{
    /// <summary>
    /// 与设备访问服务通讯的服务类
    /// </summary>
    class Communicator
    {
        public delegate void CommunicatorEvent(Communicator com);
        public event CommunicatorEvent Disconnected;
        /// <summary>
        /// 应答包头长度
        /// </summary>
        private const int ResponseHeaderSize = 23;
        /// <summary>
        /// 缓冲区大小
        /// </summary>
        private const int BufferSize = 65536;
        /// <summary>
        /// 保活时间（秒，超过该时间未有新包发送则发NOOP）
        /// </summary>
        private const int LifeTime = 10; 
        
        /// <summary>
        /// 用于通讯的SOCKET
        /// </summary>
        private Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        /// <summary>
        /// 是否已登录
        /// </summary>
        private bool _isLogin = false;

        /// <summary>
        /// 最后指令发出时间（即心跳时间）
        /// </summary>
        private DateTime _lastMessageTime;

        /// <summary>
        /// 心跳包计时
        /// </summary>
        private Timer _timer;

        /// <summary>
        /// 接收线程
        /// </summary>
        private Thread _rcvThread;

        /// <summary>
        /// 心跳计时处理过程
        /// </summary>
        /// <param name="stateInfo"></param>
        private void TimerProc(Object stateInfo)
        {
            if((DateTime.Now - _lastMessageTime).TotalSeconds >= LifeTime)
            {
                Noop();
            }

            //定时清除超时协议对象
            RequestManager.ClearTimeoutRequest();
        }

        private void RecvThreadProc()
        {
            byte[] buf = new byte[BufferSize];

            try
            {
                while (true)
                {
                    int result = _socket.Receive(buf);
                    if (result == 0) //远程主机已关闭
                    {
                        break;
                    }

                    BaseProtocol p = null;
                    Guid id = BaseProtocol.GetResponseID(buf, result);
                    CommandType commandType = BaseProtocol.GetResponseType(buf, result);
                    if(commandType == CommandType.Alert)
                    {
                        p = new AlertProtocol(id);
                    }
                    else
                    {
                        p = RequestManager.Popup(id);
                    }

                    if(p == null)
                    {
                        //未找到的协议
                        Utility.Warning("未找到请求的应答：" + id.ToString());
                    }
                    else
                    {
                        try
                        {
                            p.ParseResponse(buf, result);
                        }
                        catch(Exception e)
                        {
                            Utility.Warning("应答处理异常：" + e.Message);
                        }
                    }
                }
            }
            catch(Exception e)
            {
                Utility.Error(e.Message);
            }
            //停止计时器
            _timer.Change(Timeout.Infinite, Timeout.Infinite);
            Disconnected(this);
        }

        /// <summary>
        /// 执行登录认证会话，登录过程是同步的
        /// </summary>
        /// <param name="customerId">用户ID</param>
        /// <param name="password">密码的MD5加密</param>
        private void Login(string customerId, byte[] password)
        {
            LoginProtocol p = new LoginProtocol(customerId, password);
            byte[] buf = p.GetRequest();
            _socket.Send(buf);
            //立即等待登录应答
            buf = new byte[BufferSize];
            if (_socket.Poll(30000000, SelectMode.SelectRead)) //等待接收超时30秒
            {
                int rcvSize = _socket.Receive(buf);
                if(BaseProtocol.GetResponseID(buf, rcvSize) == p.ID &&
                    BaseProtocol.GetResponseType(buf, rcvSize) == p.Type) 
                {
                    p.ParseResponse(buf, rcvSize);
                    if(p.State == ErrorCode.Success)
                    {
                        _isLogin = true;
                        UpdateLastMessageTime();
                        //开始心跳计时
                        _timer.Change(5000, 5000);
                    }
                    else
                    {
                        throw new Exception(String.Format("登录失败 {0}", p.State));
                    }
                }
                else
                {
                    throw new Exception(String.Format("登录应答包格式不正确"));
                }
            }
            else
            {
                throw new TimeoutException("等待登录应答超时");
            }
        }

        private void UpdateLastMessageTime()
        {
            lock(_timer)
            {
                _lastMessageTime = DateTime.Now;
            }
        }

        /// <summary>
        /// 判断当前是否已通过连接和认证
        /// </summary>
        public bool IsConnected
        {
            get { return _socket.Connected; }
        }

        /// <summary>
        /// 是否已通过服务器的登录认证
        /// </summary>
        public bool IsLogin
        {
            get { return _isLogin; }
        }

        public Communicator()
        {
            //创建计时器
            _timer = new Timer(TimerProc);
            _rcvThread = new Thread(new ThreadStart(RecvThreadProc));
            //禁用Nagle
            _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buf"></param>
        private void SendData(byte[] buf)
        {
            _socket.Send(buf);
            UpdateLastMessageTime();
        }

        /// <summary>
        /// 登录到设备访问服务
        /// </summary>
        /// <param name="ipAddress">设备访问服务主机IP</param>
        /// <param name="port">端口号</param>
        /// <param name="customerId">登录的客户ID</param>
        /// <param name="password">以MD5加密的登录口令</param>
        public void Connect(string ipAddress, ushort port,
            string customerId, byte[] password)
        {
            //连接至远端主机
            while(true)
            {
                try
                {
                    _socket.Connect(ipAddress, port);
                    break;
                }
                catch(SocketException e)
                {
                    Utility.Warning("连接服务器失败，正在重试：" + e.NativeErrorCode);
                    Thread.Sleep(3000);
                }
            }
            
            try
            {
                //登录过程
                Login(customerId, password);
                //启动接收线程
                _rcvThread.Start();
            }
            catch
            {
                Close();
                throw;
            }
        }

        /// <summary>
        /// 关闭通讯
        /// </summary>
        public void Close()
        {
            if(_socket.Connected)
            {
                _socket.Shutdown(SocketShutdown.Both);
                _socket.Close();
                _isLogin = false;
                //禁止心跳计时
                _timer.Change(Timeout.Infinite, Timeout.Infinite);
                //等待接收线程停止
                _rcvThread.Join();
            }
        }

        /// <summary>
        /// 注册新节点
        /// </summary>
        /// <param name="nodeType">节点类型</param>
        /// <param name="serverIp">SmartServer IP</param>
        /// <param name="nodeName">节点名称</param>
        /// <param name="neruonID">节点ID</param>
        /// <param name="bindAlert">是否绑定告警</param>
        /// <returns></returns>
        public ErrorCode RegisterNode(byte nodeType, string serverIp,
            string nodeName, string neruonID, bool bindAlert)
        {
            try
            {
                RegisterNodeProtocol p = new RegisterNodeProtocol(nodeType, serverIp, nodeName, neruonID, bindAlert);
                SendData(p.GetRequest());
                return ErrorCode.Success;
            }
            catch(Exception e)
            {
                Utility.Error(e.Message);
                return ErrorCode.SocketFailed;
            }
        }

        /// <summary>
        /// 更新节点
        /// </summary>
        /// <param name="nodeType">节点类型</param>
        /// <param name="serverIp">SmartServer IP</param>
        /// <param name="nodeName">节点名称</param>
        /// <param name="neruonId">节点ID</param>
        /// <returns></returns>
        public ErrorCode UpdateNode(byte nodeType, string serverIp,
            string nodeName, string neruonId)
        {
            try
            {
                UpdateNodeProtocol p = new UpdateNodeProtocol(nodeType, serverIp, nodeName, neruonId);
                SendData(p.GetRequest());
                return ErrorCode.Success;
            }
            catch (Exception e)
            {
                Utility.Error(e.Message);
                return ErrorCode.SocketFailed;
            }
        }

        /// <summary>
        /// 移除节点
        /// </summary>
        /// <param name="nodeType">节点类型</param>
        /// <param name="serverIp">SmartServer IP</param>
        /// <param name="nodeName">节点别名</param>
        /// <returns></returns>
        public ErrorCode RemoveNode(byte nodeType, string serverIp,
            string nodeName)
        {
            try
            {
                RemoveNodeProtocol p = new RemoveNodeProtocol(nodeType, serverIp, nodeName);
                SendData(p.GetRequest());
                return ErrorCode.Success;
            }
            catch(Exception e)
            {
                Utility.Error(e.Message);
                return ErrorCode.SocketFailed;
            }
        }

        /// <summary>
        /// 校时
        /// </summary>
        /// <param name="nodeType">节点类型</param>
        /// <param name="serverIp">SmartServer IP</param>
        /// <param name="nodeName">节点别名</param>
        /// <param name="timingType">校时命令类型</param>
        /// <returns></returns>
        public ErrorCode Timing(byte nodeType, string serverIp,
            string nodeName, TimingType timingType)
        {
            try
            {
                TimingProtocol p = new TimingProtocol(nodeType, serverIp, nodeName, timingType);
                SendData(p.GetRequest());
                return ErrorCode.Success;
            }
            catch(Exception e)
            {
                Utility.Error(e.Message);
                return ErrorCode.SocketFailed;
            }
        }

        public ErrorCode SetTactics(byte nodeType, string serverIp,
            bool valid, byte[] weekDays, TacticRule[] rules,
            string[] nodes)
        {
            try
            {
                SetTacticsProtocol p = new SetTacticsProtocol(nodeType, serverIp, valid,
                    weekDays, rules, nodes);
                SendData(p.GetRequest());
                return ErrorCode.Success;
            }
            catch (Exception e)
            {
                Utility.Error(e.Message);
                return ErrorCode.SocketFailed;
            }
        }

        public ErrorCode Control(byte nodeType, string serverIp,
            string nodeName, float state)
        {
            try
            {
                ControlProtocol p = new ControlProtocol(nodeType, serverIp, nodeName, state);
                SendData(p.GetRequest());
                return ErrorCode.Success;
            }
            catch (Exception e)
            {
                Utility.Error(e.Message);
                return ErrorCode.SocketFailed;
            }
        }

        public ErrorCode Noop()
        {
            try
            {
                NoopProtocol p = new NoopProtocol();
                SendData(p.GetRequest());
                return ErrorCode.Success;
            }
            catch (Exception e)
            {
                Utility.Error(e.Message);
                return ErrorCode.SocketFailed;
            }
        }

        public ErrorCode GetStatus(byte nodeType, string serverIp,
            string nodeName)
        {
            try
            {
                GetStatusProtocol p = new GetStatusProtocol(nodeType, serverIp, nodeName);
                SendData(p.GetRequest());
                return ErrorCode.Success;
            }
            catch (Exception e)
            {
                Utility.Error(e.Message);
                return ErrorCode.SocketFailed;
            }
        }

        public ErrorCode SelfChecking(byte nodeType, string serverIp,
            string nodeName, SelfCheckingType selfCheckingType)
        {
            try
            {
                SelfCheckingProtocol p = new SelfCheckingProtocol(nodeType, serverIp, nodeName, selfCheckingType);
                SendData(p.GetRequest());
                return ErrorCode.Success;
            }
            catch (Exception e)
            {
                Utility.Error(e.Message);
                return ErrorCode.SocketFailed;
            }
        }
    }
}
